﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Threading;

using agsXMPP.Collections;
using agsXMPP.protocol.client;

using VSTalk.Engine.Core.Context;
using VSTalk.Engine.Core.EntitySession;
using VSTalk.Engine.Core.XmppConnection.PresenceListener;
using VSTalk.Model;
using VSTalk.Tools;

namespace VSTalk.Engine.Core.Disco.Caps
{
    public class CapsService : ICapsService, IListener
    {
        private IPresenceListener _presenceListener;
        
        private IDictionary<CapsSource, CapsInfo> _presentCaps = new Dictionary<CapsSource, CapsInfo>();

        private ICapsDiscoveryDataAccess _discoveryDataAccess;

        private ICapsDiscoveryNetAccess _discoveryNetAccess;
        private IActionWrapper<Client, Presence> _onPresence;
        private IActionWrapper<CapsInfo, IList<string>> _onFeature;

        public CapsService()
        {
            _presenceListener = new MultiClientPresenceListener();
            _discoveryDataAccess = CreateDiscoDataAccess();
            _discoveryNetAccess = CreateDiscoNetAccess();
        }

        private ICapsDiscoveryDataAccess CreateDiscoDataAccess()
        {
            var da = new CapsDiscoveryDataProvider();
            return da;
        }

        private ICapsDiscoveryNetAccess CreateDiscoNetAccess()
        {
            var net = new CapsDiscoveryNetAccess();
            return net;
        }


        public void StartListen()
        {
            var dispatcher = Dispatcher.CurrentDispatcher;
            _onPresence = dispatcher.Wrap<Client, Presence>(OnPresence);
            _onFeature = dispatcher.Wrap<CapsInfo, IList<string>>(OnFeatures);
            _discoveryNetAccess.FeaturesReceived += _onFeature.Exec;
            _presenceListener.PresenceReceived += _onPresence.Exec;
            _discoveryNetAccess.StartListen();
            _presenceListener.StartListen();
        }

        public void StopListen()
        {
            _presenceListener.StopListen();
            _discoveryNetAccess.StopListen();
            _discoveryNetAccess.FeaturesReceived -= _onFeature.Exec;
            _presenceListener.PresenceReceived -= _onPresence.Exec;
        }

        public void OnPresence(Client client, Presence presence)
        {
            var clientContext = SessionModel.GetClientContext(client);
            if (clientContext.Jid.Equals(presence.From, new CaseInsensitiveComparer()))
            {
                return;
            }
            var fromJid = presence.From.ToString().ToLower();
            var capsKey = new CapsSource(client, fromJid);
            ProcessedPresence(capsKey, presence);
        }

        private static ISessionModel SessionModel
        {
            get { return AppServices.Get<ISessionModel>(); }
        }

        private void ProcessedPresence(CapsSource capsKey, Presence presence)
        {
            var capabilities = presence.Capabilities;
            if (capabilities == null)
            {
                _presentCaps.Remove(capsKey);
                return;
            }
            var version = capabilities.Version;
            var hash = capabilities.Hash;
            var node = capabilities.Node;
            var info = new CapsInfo
            {
                Version = version,
                Hash = hash,
                Node = node,
            };

            if (_presentCaps.ContainsKey(capsKey))
            {
                var prevCaps = _presentCaps[capsKey];
                if (prevCaps == info)
                {
                    return;
                }
            }
            _presentCaps[capsKey] = info;                
            
            if (!_discoveryDataAccess.HasCaps(info))
            {
                _discoveryNetAccess.RequestFeatures(capsKey, info);
            }
        }

        private void OnFeatures(CapsInfo version, IList<string> features)
        {
            _discoveryDataAccess.RecordFeatures(version, features);
        }

        public bool HasFeature(IInterlocutorResource interlocutorResource, string uri)
        {
            var client = ModelContext.GetClientByContact(interlocutorResource.Interlocutor);
            var jid = interlocutorResource.Jid.ToString().ToLower();
            var key = new CapsSource(client, jid);
            CapsInfo capsInfo;
            if (!_presentCaps.TryGetValue(key, out capsInfo))
            {
                return false;
            }
            return _discoveryDataAccess.HasFeature(capsInfo, uri);
        }

        private static IModelContext ModelContext
        {
            get { return AppServices.Get<IModelContext>(); }
        }
    }
}